package co.edu.eam.ingsoft.tlf.analizador.logica;

import java.util.ArrayList;

/**
 * CLase que analiza un fragmento de cdigo.
 * 
 * @author Camilo Andres
 */
public class AnalizadorLexico {

	// atributos
	/**
	 * Fila donde va el analisis lexico.
	 */
	private int filaActual;
	/**
	 * COlumna actual donde va el ananlisis lexico.
	 */
	private int columnaActual;
	/**
	 * Posicion dentro del codigo donde va el analisis lexico.
	 */
	private int posicionActual;
	/**
	 * Caacter actual del analisis lexico.
	 */
	private char caracterActual;
	/**
	 * Codigo fuente a analiszar
	 */
	private String codigoFuente;
	/**
	 * Lsita de los tokends identificados en el codigo.
	 */
	private ArrayList<SimboloLexico> tablaSimbolos;
	/**
	 * Lista de lexemas no identificados.
	 */
	private ArrayList<SimboloLexico> tablaErrores;
	// constante EOC para indicar fin del archivo
	private static final char EOC = 0;

	/*
	 * A este m���todo le ingresa como par���metro el c���digo
	 * Fuente su funci���n es llamar los diferentes m���todos que
	 * definen la clasificaci���n del token, el lexema y posici���n
	 */
	public void analizar(String codigoFuente) {
		/*
		 * Inicializa la posici���n Fila y Columna en 1. Y la
		 * posici���n Actual que recorre el c���digo en 0.
		 */
		filaActual = columnaActual = 1;
		this.codigoFuente = codigoFuente;
		posicionActual = 0;
		// Crea una tabla de s���mbolos y de errores de tipo ArrayList de
		// S���mbolos L���xicos
		// para almacenar los simbolos l���xicos
		tablaSimbolos = new ArrayList<SimboloLexico>();
		// para almacenar los errores l���xicos
		tablaErrores = new ArrayList<SimboloLexico>();

		// si no se ha terminado el archivo
		if (codigoFuente.length() != EOC) {
			// extraigo el primer caracter del C���digo Fuente
			caracterActual = codigoFuente.charAt(posicionActual);
		} else {
			// a caracterActual le asigno EOC que indica la finalizaci���n
			// del
			// codigoFuente
			caracterActual = EOC;
		}

		/*
		 * Mientras existan caracteres por analizar se van llamando diferentes
		 * m���todos predicados que definir���n el s���mbolo
		 * L���xico
		 */
		while (caracterActual != EOC) {
			// van todos los m���todos predicado

			if (esComentario())
				continue;

			PalabraReservadaEnum[] palabrasReservadas = PalabraReservadaEnum
					.values();
			for (int i = 0; i < palabrasReservadas.length; i++) {

				if (esPalabraReservada(palabrasReservadas[i],
						TipoTokenEnum.PALABRA_RESERVADA)) {
					continue;
				}
			}
			if (esEntero())
				continue;

			if (esClase())
				continue;

			if (esConstante())
				continue;

			if (esMetodo())
				continue;

			if (esVariable())
				continue;

			if (esCadena())
				continue;

			if (esPaquete())
				continue;

			SimboloEnum[] simbolosEnum = SimboloEnum.values();
			for (int i = 0; i < simbolosEnum.length; i++) {

				if (esSimbolo(simbolosEnum[i],
						simbolosEnum[i].getTipoTokenEnum())) {
					continue;
				}
			}

			if (esCaracterNoImprimible())
				continue;

			// si no pertenece a nuestro lenguaje lo almacena como un
			// s���mbolo
			// no identificado
			SimboloLexico sl = new SimboloLexico(caracterActual + "",
					TipoTokenEnum.ERROR_SINTACTICO, filaActual, columnaActual);
			tablaErrores.add(sl);
			sigteCaracter();

		}
	}

	private boolean esClase() {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = caracterActual + "";

		if (caracterActual != 'O') {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		sigteCaracter();

		if (!Character.isUpperCase(caracterActual)) {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		lexema += caracterActual;
		sigteCaracter();

		while (Character.isLetter(caracterActual)) {
			lexema += caracterActual;
			sigteCaracter();
		}

		SimboloLexico sl = new SimboloLexico(lexema, TipoTokenEnum.CLASE,
				filaToken, colToken);
		tablaSimbolos.add(sl);
		return true;
	}

	private boolean esMetodo() {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = caracterActual + "";

		if (caracterActual != 'M') {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		sigteCaracter();

		if (!Character.isLowerCase(caracterActual)) {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		lexema += caracterActual;
		sigteCaracter();

		while (Character.isLetter(caracterActual)) {
			lexema += caracterActual;
			sigteCaracter();
		}

		SimboloLexico sl = new SimboloLexico(lexema, TipoTokenEnum.METODO,
				filaToken, colToken);
		tablaSimbolos.add(sl);
		return true;
	}

	private boolean esConstante() {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = caracterActual + "";

		if (caracterActual != 'C') {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		sigteCaracter();

		if (caracterActual != '_') {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		lexema += caracterActual;
		sigteCaracter();

		while (Character.isUpperCase(caracterActual) || caracterActual == '_') {
			lexema += caracterActual;
			sigteCaracter();
		}

		SimboloLexico sl = new SimboloLexico(lexema, TipoTokenEnum.CONSTANTE,
				filaToken, colToken);
		tablaSimbolos.add(sl);
		return true;
	}

	private boolean esVariable() {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = caracterActual + "";

		if (caracterActual != 'V') {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		sigteCaracter();

		if (!Character.isLowerCase(caracterActual)) {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		lexema += caracterActual;
		sigteCaracter();

		while (Character.isLowerCase(caracterActual) || caracterActual == '_') {
			lexema += caracterActual;
			sigteCaracter();
		}

		SimboloLexico sl = new SimboloLexico(lexema, TipoTokenEnum.VARIABLE,
				filaToken, colToken);
		tablaSimbolos.add(sl);
		return true;
	}

	public boolean esPalabraReservada(PalabraReservadaEnum palabrasReservadas,
			TipoTokenEnum tipoToken) {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = "";
		for (int i = 0; i < palabrasReservadas.getDescripcion().length(); i++) {
			char caracter = palabrasReservadas.getDescripcion().charAt(i);

			if (caracterActual != caracter) {
				bactracking(posBackTrack, filaToken, colToken);
				return false;
			}
			lexema += caracterActual;
			sigteCaracter();
		}
		SimboloLexico sl = new SimboloLexico(lexema, tipoToken, filaToken,
				colToken);
		tablaSimbolos.add(sl);
		return true;

	}

	public boolean esSimbolo(SimboloEnum simboloEnum, TipoTokenEnum tipoToken) {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = "";
		for (int i = 0; i < simboloEnum.getDescripcion().length(); i++) {
			char caracter = simboloEnum.getDescripcion().charAt(i);

			if (caracterActual != caracter) {
				bactracking(posBackTrack, filaToken, colToken);
				return false;
			}
			lexema += caracterActual;
			sigteCaracter();
		}
		SimboloLexico sl = new SimboloLexico(lexema, tipoToken, filaToken,
				colToken);
		tablaSimbolos.add(sl);
		return true;

	}

	private boolean esComentario() {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = caracterActual + "";

		if (caracterActual == '/') {
			sigteCaracter();
			if (caracterActual == '*') {
				lexema += caracterActual;
				sigteCaracter();
				while (caracterActual != '*') {
					lexema += caracterActual;
					sigteCaracter();
				}
				lexema += caracterActual;
				sigteCaracter();

				if (caracterActual == '/') {
					lexema += caracterActual;
					sigteCaracter();

					SimboloLexico sl = new SimboloLexico(lexema,
							TipoTokenEnum.COMENTARIO, filaToken, colToken);
					tablaSimbolos.add(sl);
					return true;
				} else {
					bactracking(posBackTrack, filaToken, colToken);
					return false;
				}
			} else {
				bactracking(posBackTrack, filaToken, colToken);
				return false;
			}
		} else {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
	}

	private boolean esCadena() {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = caracterActual + "";

		if (caracterActual == '[') {
			sigteCaracter();
			while (caracterActual != ']') {
				lexema += caracterActual;
				sigteCaracter();
			}
			lexema += caracterActual;
			sigteCaracter();

			SimboloLexico sl = new SimboloLexico(lexema, TipoTokenEnum.CADENA,
					filaToken, colToken);
			tablaSimbolos.add(sl);
			return true;

		} else {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
	}

	private boolean esPaquete() {
		int posBackTrack = posicionActual;
		int filaToken = filaActual;
		int colToken = columnaActual;
		String lexema = caracterActual + "";

		if (!Character.isLowerCase(caracterActual)) {
			bactracking(posBackTrack, filaToken, colToken);
			return false;
		}
		sigteCaracter();

		while (Character.isLowerCase(caracterActual) || caracterActual == '.') {
			lexema += caracterActual;
			sigteCaracter();
		}

		SimboloLexico sl = new SimboloLexico(lexema, TipoTokenEnum.PAQUETE,
				filaToken, colToken);
		tablaSimbolos.add(sl);
		return true;
	}

	/**
	 * metodo para determinar si un tokenn es un entero.
	 * 
	 * @return
	 */
	private boolean esEntero() {
		// Si el d���gito NO es entero retorna FALSE
		if (!Character.isDigit(caracterActual)) {
			return false;
		}

		/*
		 * Se almacena la pocisi���n Actual en posicionParaBactraking en
		 * que inicia el recorrido para definir si es un entero. En caso de no
		 * serlo, debe iniciar el recorrido en esta posici���n para
		 * continuar con otro m���todo Predicado
		 */
		int posicionParaBactraking = posicionActual;

		// posicion para guardar los lexemas en la tabla de s���mbolos
		int filaToken = filaActual;
		int colToken = columnaActual;

		// se almacena el caracterActual en la cadena lexema
		String lexema = caracterActual + "";

		// si el caracter actual es un D���gito, entonces llama al
		// siguiente
		// caracter
		if (Character.isDigit((caracterActual))) {
			sigteCaracter();
		}

		// Mientras el caracter sea un d���gito, adiciona al lexema ese
		// caracter
		// y sigue revisando
		// el siguiente caracter.
		while (Character.isDigit(caracterActual)) {
			lexema += caracterActual;
			// Avanza al siguiente caracter sobre el codigo fuente
			sigteCaracter();
		}

		/*
		 * Si el caracterActual en una coma (,) entonces el token NO es un
		 * entero, por lo tanto debemos reiniciar la b���squeda en el
		 * c���digo fuente. Se llama el metodo backtracking() ingresandole
		 * como par���metros (posicionParaBactraking, filaIniToken,
		 * columnaInicialToken) y retorna FALSO a ENTERO
		 */
		if (caracterActual == '.') {
			bactracking(posicionParaBactraking, filaToken, colToken);
			return false;
		} else {
			SimboloLexico sl = new SimboloLexico(lexema, TipoTokenEnum.ENTERO,
					filaToken, colToken);
			tablaSimbolos.add(sl);
			return true;
		}

	}

	/**
	 * Reconoce salto de l���nea
	 */
	private boolean esCaracterNoImprimible() {
		if (caracterActual == '\n' || caracterActual == ' '
				|| caracterActual == '\t' || caracterActual == '\r') {
			sigteCaracter();
			return true;
		}
		return false;
	}

	/*
	 * El m���todo bactracking se encarga de reiniciar la posicionActual,
	 * caracterActual, columnaActual,filaActual para comenzar de nuevo la
	 * b���squeda en el c���digo Fuente de un nuevo lexema
	 */
	private void bactracking(int posicionParaBactraking, int filaInicialToken,
			int columnaInicialToken) {
		posicionActual = posicionParaBactraking;
		caracterActual = codigoFuente.charAt(posicionActual);

		columnaActual = columnaInicialToken;
		filaActual = filaInicialToken;

	}

	/*
	 * El metodo sigteCaracter() se encarga de adelantar una posici���n en
	 * el c���digo Fuente
	 */
	private void sigteCaracter() { // Si el caracter le���do el igual a la
									// EOC
									// terminaci���n del archivo, entonces
									// a
		// caracterActual le asigna EOC
		System.out.println("caracter #" + posicionActual + " de "
				+ codigoFuente.length());
		if (posicionActual == codigoFuente.length() - 1) {
			caracterActual = EOC;
		} else { // Si el caracter le���do del c���digoFuente es un
					// Salto de
					// L���nea '\n' entonces incrementa
			// la fila e inicializa la columnaActual en 0
			if (codigoFuente.charAt(posicionActual + 1) == '\n') {
				filaActual++;
				columnaActual = 0;
			} else {
				// Si el caracter le���do del c���digoFuente es un
				// tabulador
				// '\t' entonces incrementa
				// la fila e inicializa la columnaActual en 4
				if (codigoFuente.charAt(posicionActual + 1) == '\t') {
					columnaActual += 4;
				} else {
					columnaActual++;
				}
			}
			posicionActual++;
			caracterActual = codigoFuente.charAt(posicionActual);
		}
	}

	/*
	 * El m���todo getTablaSimbolos() retorna el arreglo de
	 * s���mbolos l���xicos encontrados en el c���digo Fuente
	 */
	public ArrayList<SimboloLexico> getTablaSimbolos() {
		return tablaSimbolos;
	}

	/*
	 * El m���todo getTablaErrores() retorna la tabla de errores
	 * encontrados en el c���digo Fuente
	 */
	public ArrayList<SimboloLexico> getTablaErrores() {
		return tablaErrores;
	}

}
